﻿using System;
using System.ComponentModel;
using System.Windows;
using AMail.Common;
using AMail.Common.Events;
using AMail.Common.Interfaces.Data;
using AMail.Common.Interfaces.Services;
using Elentok.Common;
using Elentok.Common.Wpf;
using Elentok.Common.Wpf.Dialogs;
using Elentok.Common.Wpf.ViewModels.Dialogs;
using Microsoft.Practices.Composite.Events;
using Microsoft.Win32;
using Elentok.Common.Async;
using Elentok.Common.Wpf.Services;

namespace AMail.Modules.Archive.Services
{
  public class ArchiveService : IArchiveService
  {
    #region Private Fields
    private IDataService dataService;
    private IEventAggregator eventAggregator;
    private IRecentFilesService recentFilesService;
    private ICommonDialogsService commonDialogsService;

    private IMailArchive activeArchive;
    #endregion

    #region Ctor
    public ArchiveService(IDataService dataService,
      IEventAggregator eventAggregator,
      IRecentFilesService recentFilesService,
      ICommonDialogsService commonDialogsService)
    {
      this.dataService = dataService;
      this.eventAggregator = eventAggregator;
      this.recentFilesService = recentFilesService;
      this.commonDialogsService = commonDialogsService;

      eventAggregator.GetEvent<CanExitEvent>().Subscribe(OnCanExit);
      recentFilesService.OpenFileRequested += RecentFiles_OpenFileRequested;
    }
    #endregion

    #region Event Handlers
    
    private void RecentFiles_OpenFileRequested(object sender, OpenFileRequestedEventArgs e)
    {
      e.Success = this.OpenArchive(e.FileName);
    }

    /// <summary>
    /// When the application tries to exit, this method
    /// checks if the active archive has unsaved changes
    /// and if so, it asks the user if he wishes to discard the changes
    /// or save them.
    /// </summary>
    /// <param name="e"></param>
    private void OnCanExit(CancelEventArgs e)
    {
      if (!CanCloseActiveArchive())
        e.Cancel = true;
    }
    
    #endregion

    #region Open Archive

    /// <summary>
    /// Opens the OpenFileDialog and then tries to open
    /// the file selected by the user (shows the password dialog, etc...)
    /// </summary>
    /// <returns></returns>
    public bool OpenArchive()
    {
      if (!CanCloseActiveArchive())
        return false;

      OpenFileDialog dialog = new OpenFileDialog();
      dialog.Filter = AMailStrings.FileDialogFilter;
      dialog.DefaultExt = AMailStrings.DefaultExtension;
      bool? result = dialog.ShowDialog(Application.Current.MainWindow);
      if (result == true)
        return InnerOpenArchive(dialog.FileName);

      return false;
    }

    /// <summary>
    /// Try to open the requested archive file
    /// (displays the password dialog, etc...).
    /// Returns true if file was opened successfuly.
    /// </summary>
    /// <param name="filename"></param>
    /// <returns></returns>
    public bool OpenArchive(string filename)
    {
      if (!CanCloseActiveArchive())
        return false;

      return InnerOpenArchive(filename);
    }

    /// <summary>
    /// Try to open the requested archive file
    /// (displays the password dialog, etc...).
    /// Returns true if file was opened successfuly.
    /// (WARNING: DOES NOT CHECK IF ACTIVE ARCHIVE HAS UNSAVED CHANGES)
    /// </summary>
    /// <param name="filename"></param>
    /// <returns></returns>
    private bool InnerOpenArchive(string filename)
    {
      var passwordDialog = new PasswordDialog();
      while (true)
      {
        passwordDialog.Owner = Application.Current.MainWindow;
        var result = passwordDialog.ShowDialog();

        if (result == true)
        {
          if (InnerOpenArchive(filename, passwordDialog.Password))
            return true;
          else
          {
            passwordDialog = new PasswordDialog();
            passwordDialog.ErrorMessage = "Invalid password";
          }
        }
        else
          return false;
      }
    }

    /// <summary>
    /// Tries to open the given file, returns false if the password is wrong
    /// (WARNING: DOES NOT CHECK IF ACTIVE ARCHIVE HAS UNSAVED CHANGES)
    /// </summary>
    /// <param name="filename"></param>
    /// <param name="password"></param>
    /// <returns></returns>
    private bool InnerOpenArchive(string filename, string password)
    {
      IMailArchive archive = null;

      try
      {
        archive = this.dataService.OpenArchive(filename, password);
      }
      catch (WrongPasswordException)
      {
        return false;
      }

      this.ActiveArchive = archive;
      return true;
    }

    #endregion

    /// <summary>
    /// Try to create a new archive (ask the user for
    /// the name and password of the new archive).
    /// Returns true if a new archive was created.
    /// </summary>
    /// <returns></returns>
    public bool CreateNewArchive()
    {
      if (!CanCloseActiveArchive())
        return false;

      // save dialog
      SaveFileDialog saveFileDialog = new SaveFileDialog();
      saveFileDialog.Filter = AMailStrings.FileDialogFilter;
      saveFileDialog.DefaultExt = AMailStrings.DefaultExtension;
      bool? result = saveFileDialog.ShowDialog(Application.Current.MainWindow);
      if (result != true)
        return false;

      // new password dialog
      string password;
      if (!this.AskUserForPassword(out password))
        return false;

      // create the new archive
      var archive = this.dataService.CreateArchive(saveFileDialog.FileName, password);

      this.ActiveArchive = archive;

      return true;
    }

    /// <summary>
    /// Tries to close the active archive
    /// (displays a confirmation if it has unsaved changes).
    /// Returns true if it was closed sucessfully.
    /// </summary>
    /// <returns></returns>
    public bool CloseActiveArchive()
    {
      if (!CanCloseActiveArchive())
        return false;

      this.ActiveArchive = null;
      return true;
    }

    #region Save Archive

    /// <summary>
    /// Saves the active archive.
    /// Returns <c>true</c> if the archive was saved successfuly.
    /// </summary>
    /// <returns></returns>
    public bool SaveActiveArchive()
    {
      var saveAction = new DelegateAsyncTask("Saving...", 
        delegate(IAsyncProgressNotifier notifier)
        {
          this.ActiveArchive.Save(notifier);
        });

      this.commonDialogsService.OpenProgressDialog(saveAction, true);

      return saveAction.State == AsyncTaskState.Complete;
    }

    #endregion

    /// <summary>
    /// If the active archive has unsaved changes, this method
    /// will show the user a message asking whether he would like
    /// to save the changes, discard the changes or cancel.
    /// <para>
    /// If the user selects "Yes" it will try to save the changes
    /// and if sucessful, it will return true (otherwise returns false).
    /// </para>
    /// <para>
    /// If the user selects "No" it will return true.
    /// </para>
    /// <para>
    /// If the user selects "Cancel" it will return false.
    /// </para>
    /// </summary>
    /// <returns></returns>
    public bool CanCloseActiveArchive()
    {
      if (this.ActiveArchiveHasUnsavedChanges)
      {
        var viewModel = new ButtonsDialogViewModel();
        viewModel.Title = "Archive has unsaved changes";
        viewModel.Message = "What do you want to do?";
        var saveButton = new ButtonViewModel { Header = "Save Changes", IsDefault = true };
        var discardButton = new ButtonViewModel { Header = "Discard Changes" };
        var cancelButton = new ButtonViewModel { Header = "Cancel", DialogResult = false };
        viewModel.Buttons.Add(saveButton);
        viewModel.Buttons.Add(discardButton);
        viewModel.Buttons.Add(cancelButton);

        var dialog = new Dialog(viewModel);
        dialog.Owner = Application.Current.MainWindow;
        if (dialog.ShowDialog() != true) // cancel
          return false;

        //var result = MessageBox.Show(
        //  "Archive has unsaved changes. Save?",
        //  "AMail - Confirm",
        //  MessageBoxButton.YesNoCancel, MessageBoxImage.Question);

        //if (result == MessageBoxResult.Yes)
        if (viewModel.SelectedButton == saveButton)
        {
          SaveActiveArchive();
          return false;
        }
        else // discard button
          return true;
      }

      return true;
    }

    /// <summary>
    /// Returns true if there's an active archive
    /// and it has unsaved changes
    /// </summary>
    public bool ActiveArchiveHasUnsavedChanges
    {
      get
      {
        return this.ActiveArchive != null &&
          this.ActiveArchive.HasUnsavedChanges;
      }
    }

    /// <summary>
    /// Returns the active archive
    /// </summary>
    public IMailArchive ActiveArchive
    {
      get { return this.activeArchive; }
      set
      {
        if (this.activeArchive == value)
          return;

        var oldArchive = this.activeArchive;
        this.activeArchive = value;

        #region Add to recent files
        if (this.activeArchive != null)
          this.recentFilesService.AddFile(this.activeArchive.Filename);
        #endregion

        #region Publish "ActiveArchiveChanged" event
        eventAggregator.GetEvent<ActiveArchiveChangedEvent>().Publish(
          new ActiveArchiveChangedEventArgs(oldArchive, this.activeArchive));
        #endregion
      }
    }

    /// <summary>
    /// Reverts to the saved version of the archive (reopens the archive)
    /// </summary>
    public void Revert()
    {
      if (!CanCloseActiveArchive())
        return;

      this.InnerOpenArchive(this.ActiveArchive.Filename, this.ActiveArchive.Passphrase);
    }

    private bool AskUserForPassword(out string password)
    {
      INewPasswordDialogViewModel viewModel = new NewPasswordDialogViewModel();
      viewModel.AllowEmptyPassword = true;
      var dialog = new Dialog(viewModel);
      dialog.Owner = Application.Current.MainWindow;
      if (dialog.ShowDialog() == true)
      {
        password = viewModel.Password;
        return true;
      }

      password = null;
      return false;
    }

    /// <summary>
    /// Changes the archive's password
    /// </summary>
    public void ChangePassword()
    {
      string password;
      if (!AskUserForPassword(out password))
        return;

      var task = new DelegateAsyncTask("Changing password...",
        delegate(IAsyncProgressNotifier notifier)
        {
          this.ActiveArchive.ChangePassword(password, notifier);
        });

      this.commonDialogsService.OpenProgressDialog(task, true);
    }

  }
}
